/*════════════════════════════╗════════════════════════════════════════════════════════════════════════════════════════════════╗
║     SKIN-SPECIFIC HOOKS    ║ I'll put some custom character hooks so other people can take note of how to add their own.	   ║
╚════════════════════════════╝═════════════════════════════════════════════════════════════════════════════════════════════════║
║												[DOCUMENTATION LINK]														   ║
║		  There's extra info on custom character support documented here, though copy-pasting any of this is totally fine.	   ║
║					  https://docs.google.com/document/d/1qsdKvVtm__wwlC9Jl4Ibc20onf7jkUx_rDFPqx4UnZ8						   ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
GS Rails has a system to let you create custom "hooks" that only affect your own characters. 
You can look at the other specific skin files for reference. Adventure Sonic's shows and explains every possible custom hook type.

═══════════════════════════════-- First, a legend on my coding style --═══════════════════════════════════════
p: refers to player and will always be valid
s: refers to player.mo and will always be valid
GS: refers to player.mo.GSgrind and will always be valid, unless you've forcibly deleted it yourself.
rail: refers to player.mo.GSgrind.myrail. This will NOT always be valid!

Note that you may name these whatever you personally prefer.
══════════════════════════════════════════════════════════════════════════════════════════════════════════════*/
local RAIL = GS_RAILS if RAIL.blockload return end 

--First, add this table if it doesn't exist yet.
if GS_RAILS_SKINS==nil rawset(_G,"GS_RAILS_SKINS",{}) end

--Default grinding stats! Any stats you don't specify in a rails "S_SKIN" default to these.
--CANCROUCH and AUTOBALANCE are exceptions. By default, anyone with grind sprites has CANCROUCH, otherwise they get AUTOBALANCE.
GS_RAILS_SKINS["Default"] = {
JUMP = true, --You can jump off rails with the jump button!
JUMPBOOST = true, --Gain a minor jumpboost when jumping off a rail? (Up to a certain limit.)
AIRDRAG = 44, --Airdrag time in tics after hopping off a rail. (35 tics = 1 second)

SIDEHOP = 36, --Specifies the speed of your sidehop speed. If 0, you can't sidehop at all!
SIDEHOPARC = 0, --If not 0, adds a vertical arc when sidehopping, creating an "arced" sidehop like in modern games.
SIDEFLIP = nil, --Do a Modern-style sideflip? Specify a sprite here to use for it! (I recommend SPR2_SPNG for most chars)
SIDEHOPTIME = 9, --How many tics your sidehop is considered active. If you have a slow sidehop, you probably want more time.
SIDEHOPCAMTIME = nil, --How many tics the railcam remains active after a sidehop. Otherwise the game decides automatically.

EMERGENCYDROP = true, --If true, you can press CUSTOM3 to emergency-drop through rails.
CROUCH = true, --If true, you can "Focus Grind" by holding SPIN, gaining more favorable slope momentum at the cost of balance.
CROUCHBUTTON = BT_SPIN, --Button to Focus Grind with. If set to BT_SPIN|BT_CUSTOM1, for example, it'd be either SPIN or CUSTOM1! 
AUTOBALANCE = false, --You auto-balance on rails, losing less speed and making it near-impossible to fall off.
SLOPEPHYSICS = true, --If false, slope physics are ignored.
MUSTBALANCE = false, --The character must always balance as if crouching, risking falling off at all times like in SA2!

SLOPEMULT = 0, --Percentage modifier of slope momentum. 10 would add 10% more, while -20 would reduce the effect by 20%.
STARTMODIFIER = 0, --Speed percentage modifier when attaching to rails. 10 would increase speed by 10%, while -20 decreases it 20%
STARTSPEEDCONDITION = 0, --Minimum Speed you must be at before your start speed modifier takes place. If 0, defaults to 30.

HEROESTWIST = 15, --The boostpower of the "Twist Drive", diminishing overtime. If 1, it's cosmetic. If 0, you CAN'T Twist Drive!
TWISTBUTTON = 0, --What button to press to perform the "Twist Drive". If 0, it mimicks the crouch button.
ACCELSPEED = 0, --If not 0, you can forcibly accelerate on rails by simply holding forwards. Tails has this, for example.
ACCELCAP = 48, --This is the speed cap for your ACCELSPEED. Defaults to 48.
AUTODASHMODE = 72, --At what grinding speed you automatically enter dashmode if you have SF_DASHMODE.

MINSPEED = 0, --Minimum speed your character always has when grinding. If not 0, your character can NEVER reverse!
MAXSPEED = 180, --Your character won't gain more slopespeed than this. If 0, it's infinite.

---NOTE: For a list sprite2 animations, check: bit.ly/GSrailsprites

FORCESPRITE = nil, --Force a sprite2 when grinding, overriding both SPR2_GRND as well as the backup pose.
FORCEHANGSPRITE = nil, --Force a sprite2 when on hangrails, rather than SPR2_RIDE
WALLCLINGSPRITE = nil, --Use this sprite2 when readying for a walljump? Otherwise, picks sprite automatically.
NOWINDLINES = false, --If true, doesn't spawn windlines when moving fast on rails.
NOSIDEHOPGFX = 0, --If 1, only spawns sidehop ghosts. If 2, only spawns sidehop speedlines. If 3, spawns neither.
AUTOVERTICALAIM = true, --If set to false, vertical aim will not be autoadjusted while grinding. Loops/Sidecams excepted.
NOFUNALLOWED = false, --If true, your character cannot use dunce poses with TOSS FLAG.

SPEEDTRAIL = nil, --Enter a number to make your character spawn a speedtrail at certain speeds. (60 = Thok Speed) 
--Enter "none" to spawn no speedtrails unless the rail itself has a gimmick spawning them. 
--Enter "never" to absolutely NEVER allow speedtrails.
--Leave it nil or set it to "auto" to let the game decide.

SPEEDTRAILCOLOR = nil, --Enter a SKINCOLOR_ variable to force your character's speedtrail to ALWAYS be this color.
}


--══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
--I'll include the vanilla cast below. bit.ly/GSraildocs has an example mod you're probably better using at reference than this!
--══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════

if not GS_RAILS_SKINS["sonic"]
	--Vanilla Sonic uses default stats, with a bit of extra max speed.
	GS_RAILS_SKINS["sonic"] = {}
	local SKIN = GS_RAILS_SKINS["sonic"]
	
	SKIN["S_SKIN"] = {
	MAXSPEED = 200, --Vanilla Sonic basically uses default stats, but his maxspeed is slightly above most!
	SPEEDTRAIL = "none",
	}
end

local SS2 = SPR2F_SUPER if SS2==nil SS2 = FF_SPR2SUPER or 0 end --For 2.2.13 & 2.2.14 cross compatibility.

if not GS_RAILS_SKINS["tails"]
	--Adjustments for Tails, who kind of sucks at grinding, but can accelerate himself very easily and has a minimum speed.
	GS_RAILS_SKINS["tails"] = {}
	local SKIN = GS_RAILS_SKINS["tails"]
	
	SKIN["S_SKIN"] = {
	SIDEHOP = 32,
	SIDEHOPARC = 20, --Has a big, slow, clumsy sidehop.
	SIDEHOPTIME = 13,
	
	CROUCH = false, --Cannot Focus Grind.
	AUTOBALANCE = true,
	
	STARTMODIFIER = -50,
	SLOPEMULT = -8, --Tails is less affected by slopes, both positively and negatively.
	HEROESTWIST = 14, 
	ACCELSPEED = 60, --Tails can spin his tails to accelerate!
	MINSPEED = 8,
	MAXSPEED = 148, --Lowest maxspeed of all characters.
	
	SPEEDTRAIL = "none",
	}
	
	--All this for the stupid tails...
	SKIN["CustomAnim"] = function(p, s, GS, rail, POSTTHINK, ANIMCALL)
		if (POSTTHINK!=true) return false end 
		
		local tails = p.followmobj
		if tails and tails.valid and not (tails.flags2 & MF2_DONTDRAW)
			if GS.tails_fourframe == nil GS.tails_fourframe = A end
			if (leveltime%2)
				GS.tails_fourframe = $+1 if (GS.tails_fourframe > D) GS.tails_fourframe = A end
			end		
			local SPRITE2 = (s.sprite2&~SS2)
			if not ((SPRITE2 >= SPR2_TAL0) and (SPRITE2 <= SPR2_XTRA))
			and (SPRITE2!=SPR2_DEAD) and (SPRITE2!=SPR2_DRWN)
				if (p.cmd.forwardmove > 32) and not (GS.flipanim)
					if (tails.state!=S_TAILSOVERLAY_RUN)
						tails.state = S_TAILSOVERLAY_RUN
						tails.tics, tails.anim_duration = 1,1
					end
					if ANIMCALL=="forcebutnotskip" and rail and rail.valid and ((GS.grinding or 0)>3)
						local CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
						if not (abs(GS.railspeed+CONVEYOR) > 47*s.scale)   --Speed lines!
							RAIL.SpeedLines(s, GS, RAIL.ANG(rail)+GS.raildirection)
						end
						if not S_SoundPlaying(s, sfx_putput)
							S_StartSoundAtVolume(s, sfx_putput, 99)
						end	
					end			
				elseif (tails.state!=S_TAILSOVERLAY_STAND)
					tails.state = S_TAILSOVERLAY_STAND
					tails.tics, tails.anim_duration = 3,3
				end
				
				tails.frame = ($ & ~FF_FRAMEMASK)|GS.tails_fourframe
				if not (s.flags2 & MF2_DONTDRAW)
					tails.flags2 = $ & ~MF2_DONTDRAW
				end
				local RAD = max(s.radius/3, 13*s.scale) / ((tails.state==S_TAILSOVERLAY_RUN) and 1 or 3)
				P_MoveOrigin(tails, s.x-FixedMul(RAD, cos(p.drawangle)), s.y-FixedMul(RAD, sin(p.drawangle)), s.z)
				if tails and tails.valid
					tails.rollangle = s.rollangle
					tails.mirrored = s.mirrored
					tails.spritexoffset,tails.spriteyoffset = s.spritexoffset,s.spriteyoffset
					tails.spritexscale,tails.spriteyscale = s.spritexscale,s.spriteyscale	
					tails.angle = p.drawangle
				end
				if GS.animcall=="flipanim"
					GS.animcall = "stop"
				end
			end
			if tails and rail and rail.valid	
				local LOOP = RAIL.LearnLoopType(rail)
				if LOOP and LOOP.walljump
					tails.rollangle = 0
					tails.mirrored = false
					tails.spritexoffset,tails.spriteyoffset = s.spritexoffset,s.spriteyoffset
					tails.spritexscale,tails.spriteyscale = s.spritexscale,s.spriteyscale	
					tails.angle = p.drawangle					
				end
			end
		end
	end
		
	--Aaaand AGAIN for hanging.
	SKIN["CustomHangAnim"] = function(p, s, GS, rail)	
		local tails = p.followmobj
		if tails and tails.valid and not (tails.flags2 & MF2_DONTDRAW)	
			if GS.tails_fourframe == nil GS.tails_fourframe = A end
			if (leveltime%2)
				GS.tails_fourframe = $+1 if (GS.tails_fourframe > D) GS.tails_fourframe = A end
			end		
			local SPRITE2 = (s.sprite2&~SS2)
			
			if (p.cmd.forwardmove > 32)
				if (tails.state!=S_TAILSOVERLAY_RUN)
					tails.state = S_TAILSOVERLAY_RUN
					tails.tics, tails.anim_duration = 1,1
				end
				if rail and rail.valid and ((GS.grinding or 0)>3)
					local CONVEYOR = (rail.GSconveyor) and rail.GSconveyor<<16 or 0
					if not (abs(GS.railspeed+CONVEYOR) > 47*s.scale)   --Speed lines!
						RAIL.SpeedLines(s, GS, RAIL.ANG(rail)+GS.raildirection)
					end
					if not S_SoundPlaying(s, sfx_putput)
						S_StartSoundAtVolume(s, sfx_putput, 99)
					end
				end
			elseif (tails.state!=S_TAILSOVERLAY_STAND)
				tails.state = S_TAILSOVERLAY_STAND
				tails.tics, tails.anim_duration = 3,3
			end
			tails.frame = ($ & ~FF_FRAMEMASK)|(GS.tails_fourframe or A)
			local RAD = max(s.radius/3, 13*s.scale) / ((tails.state==S_TAILSOVERLAY_RUN) and 1 or 3)
			P_MoveOrigin(tails, s.x-FixedMul(RAD, cos(p.drawangle)), s.y-FixedMul(RAD, sin(p.drawangle)), s.z)
			if tails and tails.valid
				tails.rollangle = s.rollangle
				tails.mirrored = s.mirrored
				tails.spritexoffset,tails.spriteyoffset = s.spritexoffset,s.spriteyoffset+(6<<16)
				tails.spritexscale, tails.spriteyscale = s.spritexscale,s.spriteyscale	
				tails.angle = p.drawangle
			end
		end
	end
	
	--Make sure the tails are fixed when we're done!
	SKIN["PostExit"] = function(p, s) 
		local tails = p.followmobj
		if tails and tails.valid
			tails.frame = $ & ~FF_VERTICALFLIP
			tails.scale = s.scale
			tails.rollangle = s.rollangle
			tails.mirrored = s.mirrored
			tails.spritexoffset,tails.spriteyoffset = s.spritexoffset,s.spriteyoffset
			tails.spritexscale,tails.spriteyscale = s.spritexscale,s.spriteyscale
		end
	end
	
	--And now this for the SA2-trick.
	SKIN["PlayerThink"] = function(p, s, GS, rail)
		if not (GS) return end local tails = p.followmobj
		if not (tails and tails.valid) return end --
		
		if not (GS.grinding) //GS.JumpRamp or GS.leftrail or GS.failroll or GS.railswitching or GS
			tails.rollangle = s.rollangle
			tails.mirrored = s.mirrored
			tails.spritexoffset,tails.spriteyoffset = s.spritexoffset,s.spriteyoffset
			tails.spritexscale,tails.spriteyscale = s.spritexscale,s.spriteyscale	
		end
	end	
end

if not GS_RAILS_SKINS["knuckles"]
	--Knuckles is VERY similar to Sonic, but heavier and thus more affected by slopes. Grinding+Gliding is one powerful combo! 
	GS_RAILS_SKINS["knuckles"] = {}
	local SKIN = GS_RAILS_SKINS["knuckles"]
	
	SKIN["S_SKIN"] = {
	SIDEHOP = 35,
	SIDEHOPARC = -8, --Very heavy sidehop that sends him downwards.
	SIDEHOPTIME = 11, 
	SLOPEMULT = 30, --Affected very strongly by slopes, negatively and positively!
	HEROESTWIST = 12, --Lamer Heroes Twist.
	
	SPEEDTRAIL = "none",
	}
	
	SKIN["PreAttach"] = function(p, s, GS, rail)
		if (p.pflags & PF_GLIDING) --Slow down a bit when landing on a rail gliding.
			s.momx,s.momy = $*7/8, $*7/8
		end
	end
	
	SKIN["CustomJump"] = function(p, s, GS,rail, SIDEJUMP)
		p.pflags = $|PF_JUMPDOWN & ~(PF_GLIDING|PF_THOKKED)
		if GS_RAILS.GrindState(s, "hang")
		or GS.JumpRamp and GS.JumpRamp.timer
		or GS.LTAB and GS.LTAB.snappedtofloor==true
		or GS.walljump and GS.walljump.timer
			return --
		end
		GS.forcesprite = SPR2_LAND
		if GS.railspeed and (abs(GS.railspeed) < 60*s.scale)
			GS.railspeed = ($*7/6) --Gain a bit of speed from regular jumps.
		end
	end
	SKIN["CustomSideHop"] = function(p, s, GS,rail, SIDEJUMP)
		p.pflags = $|PF_JUMPDOWN & ~(PF_GLIDING|PF_THOKKED)
		if GS_RAILS.GrindState(s, "hang") return end
		GS.forcesprite = SPR2_LAND
	end
	
	SKIN["PlayerThink"] = function(p, s, GS,rail)
		if GS.railswitching
			if (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and not GS.grinding and not P_IsObjectOnGround(s)
			and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING|PF_STARTDASH)) and not p.powers[pw_carry]	
				if (GS.railswitching > 1)
					if (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and not GS.grinding and not P_IsObjectOnGround(s)
					and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING|PF_STARTDASH)) and not p.powers[pw_carry]
						p.pflags = $|PF_JUMPDOWN & ~(PF_GLIDING|PF_THOKKED)
						if (GS.railswitching < 6)
							GS.forcesprite = SPR2_SPIN
						elseif (GS.railswitching > 10)
							GS.forcesprite = SPR2_LAND
						else
							if (s.state!=S_PLAY_FALL)
								s.state = S_PLAY_FALL
							end
							s.tics,s.anim_duration = 1,1
						end
					end
				elseif (p.cmd.buttons & BT_JUMP)
					p.pflags = $ & ~PF_JUMPDOWN --Auto glide with your momentum going forwards!
					local ghs = P_SpawnGhostMobj(s)
					ghs.scale = s.scale
					ghs.fuse,ghs.tics = 7,8
					ghs.momx,ghs.momy = s.momx/2,s.momy/2
					ghs.destscale = 1
					if (s.momz*P_MobjFlip(s) < 0)
						s.momz = $/2 --Negate falling speed!
					end				
					local THRUST = abs(s.momx)+abs(s.momy)
					s.momx,s.momy = $-(s.momx/3), $-(s.momy/3)
					P_Thrust(s, p.drawangle, ((THRUST/3)>40*s.scale) and max(40*s.scale, THRUST/4) or THRUST/3)
					S_StartSoundAtVolume(s, sfx_s1b8, 190)
				end
			end
		end
	end
end

if not GS_RAILS_SKINS["fang"]
	--Fang is a bouncy boy, but sucks at balancing.
	GS_RAILS_SKINS["fang"] = {}
	local SKIN = GS_RAILS_SKINS["fang"]
	
	SKIN["S_SKIN"] = {
	SIDEHOP = 11, --Painfully slow...
	SIDEHOPARC = 33, --...but look at that AIR he's catching!
	SIDEHOPTIME = 22,
	SIDEFLIP = SPR2_BNCE, --Has a fancy bounce-sideflip for his sidehop..
	SIDEHOPCAMTIME = 38, --Really long cam time.
	
	CROUCH = false, 
	AUTOBALANCE = false,
	MUSTBALANCE = true, --Balance well or ELSE.
	HEROESTWIST = 25, --The best Twist Drive.
	MAXSPEED = 155, --Fang's max speed is the second slowest.
	
	NOSIDEHOPGFX = 3,
	
	SPEEDTRAIL = "none",
	}
	
	--Fang bounces way high off rails instead of jumping.
	SKIN["CustomJump"] = function(p, s, GS, rail, RAMPJUMP)
		if GS_RAILS.GrindState(s, "hang")
		or GS.walljump and GS.walljump.timer
		or GS.JumpRamp and (GS.JumpRamp.timer or GS.JumpRamp.ropesnap)
		or GS.ropesnap and RAMPJUMP
		or RAMPJUMP==true
		or GS.LTAB and GS.LTAB.snappedtofloor==true
			return --
		end
		local ZFLIP = P_MobjFlip(s) or 1
		GS_RAILS.ExitRail(s, true, "jump")
		
		for i = 1,5 P_SpawnSkidDust(p, 64<<16) end
		s.momx,s.momy = $*3/4,$*3/4 --Fang trades speed for more height here.
		s.momz = $+(9*s.scale*ZFLIP)
		if (s.momz*ZFLIP < 18*s.scale)
			s.momz = 18*s.scale*ZFLIP
		end	
		s.state = S_PLAY_BOUNCE
		S_StartSound(s, sfx_boingf) --Play bounce sound instead of rail hop.
		p.pflags = $|PF_JUMPDOWN|PF_NOJUMPDAMAGE|PF_THOKKED|PF_BOUNCING & ~(PF_SHIELDABILITY|PF_JUMPED|PF_SPINNING)
		
		s.rollangle,s.spriteyoffset,s.spritexoffset = 0,-4<<16,0 --Bouncy effect.
		s.spritexscale,s.spriteyscale = 30000,105500
		GS.stretch = {timer = 8, Xspeed = 3950, Yspeed = 5000}	
		
		GS.justtouchedrailthing = 9
		GS.cannotgrind = 9
		if rail and ((rail.GSjumpflags or 0) & (2097152|1048576)) --Super or Mega jump height?
			S_StartSound(s, sfx_s1bb) S_StartSoundAtVolume(s, sfx_sprong, 88)
			for i = 1,5 P_SpawnSkidDust(p, 62<<16) end
			if not (rail.GSjumpflags & 2097152) --Super JumpHeight!
				s.momz = $+(7*s.scale*P_MobjFlip(s))
			else --Mega jumpheight! Stackable with Super for Titan jumpheight!
				s.momz = $+( ((rail.GSjumpflags & 1048576) and 27 or 18)*s.scale*P_MobjFlip(s) )
				S_StartSoundAtVolume(s, sfx_mswing, 210)
			end
		end
			
		
		return true --This returns true, so completely overrides the regular jump!
	end
	
	SKIN["CustomSideHop"] = function(p, s, GS,rail, SIDEJUMP)
		if GS_RAILS.GrindState(s, "hang") return end
		S_StopSoundByID(s, sfx_gsral9)
		S_StartSound(s, sfx_boingf) --Play bounce sound instead of rail hop.
		
		s.rollangle,s.spriteyoffset,s.spritexoffset = 0,-4<<16,0 --Bouncy effect.
		s.spritexscale,s.spriteyscale = 30000,105500
		GS.stretch = {timer = 8, Xspeed = 3950, Yspeed = 5000}	
		
		return "nostretch"
	end
	
	SKIN["PlayerThink"] = function(p, s, GS,rail)
		if GS.railswitching and not GS.grinding
			if (GS.railswitching >= 18)
				GS.forcesprite = (GS.railswitching==18) and SPR2_BNCE or SPR2_LAND
			end
			p.cmd.sidemove = 0
		end
	end
end


if not GS_RAILS_SKINS["amy"] --Only add if there's no table already.

	--Vanilla Amy is a great grinder for starters. Good stats all around, but a lower maxspeed most newbies will probably never reach.
	GS_RAILS_SKINS["amy"] = {} 
	local SKIN = GS_RAILS_SKINS["amy"]
	
	SKIN["S_SKIN"] = {
	SIDEHOP = 29,
	SIDEHOPARC = 20,
	SIDEHOPTIME = 13,
	SIDEFLIP = SPR2_MLEL, --Hammerspin sidehop, with an actual AoE radius!
	
	MUSTBALANCE = true, --Must balance! 
	CROUCH = true, 
	HEROESTWIST = 21, --And has a great Twist Drive to negate slopes with.
	MAXSPEED = 165, --A bit slower than the rest.
	
	NOSIDEHOPGFX = 1,
	
	SPEEDTRAIL = "none",
	}
	
	local function AMYHEARTIMPACT(p, s, GS)
		local ANG,ghs = p.drawangle
		for i = 1,8
			for d = 1,2
				ghs = P_SpawnMobjFromMobj(s, FixedMul(48*s.scale/d, cos(ANG)), FixedMul(48*s.scale/d, sin(ANG)), 1, 
				(d==2) and MT_EXPLODE or MT_LHRT)
				if (d==1)
					if ghs and ghs.valid
						if GS and GS.railspeed P_Thrust(ghs, p.drawangle, abs(GS.railspeed)) end
						ghs.momx,ghs.momy = $+s.momx,$+s.momy
						P_Thrust(ghs, ANG, 64*s.scale)
						ghs.momz = 8*s.scale*P_MobjFlip(s)
						ghs.scale = s.scale/2
						ghs.scalespeed = FRACUNIT/31
						ghs.destscale = s.scale*2
						ghs.target,ghs.tracer = s,s
						ghs.fuse = 18
						ghs.renderflags = $|RF_SEMIBRIGHT
					end
					if i==8 ANG = ANGLE_22h end
				end
			end ANG = $+ANGLE_45
		end
	end
	
	SKIN["CustomSideHop"] = function(p, s, GS,rail, SIDEJUMP)
		if GS_RAILS.GrindState(s, "hang") return "hangflip" end
		S_StopSoundByID(s, sfx_gsral9)
		S_StartSound(s, sfx_s3k8b) --Play hammer sound instead of rail hop.
		AMYHEARTIMPACT(p, s, GS)
		P_RadiusAttack(s, s, 200<<16) --Minor AoE damage!
	end
	
	--You're hitting the rail with your hammer! ...but why?
	SKIN["PreAttach"] = function(p, s, GS, mainrail, seg)
		if (p.panim==PA_ABILITY) and (p.pflags & PF_THOKKED) and (p.pflags & PF_NOJUMPDAMAGE) and (p.pflags & PF_JUMPED)
			if mainrail and (mainrail.GSinvisiblerail or (mainrail.flags2 & MF2_DONTDRAW) or mainrail.sprite==SPR_NULL)
				return --It's weird doing this off invisible rails.
			end
			local TMOMZ = (s.momz*2/3)*P_MobjFlip(s)
			local SEG = seg or mainrail
			if (abs(TMOMZ) < 10*s.scale)
				s.momz = 10*s.scale*P_MobjFlip(s)
			else
				s.momz = abs(TMOMZ)*P_MobjFlip(s)
			end
			if mainrail and mainrail.GSrailZDelta and (abs(mainrail.GSrailZDelta) > ANG1) --A little slopethrust too, why not.
				local ZDELTA = mainrail.GSrailZDelta
				local THRUST = (ZDELTA/(ANG1/4))*s.scale 
				if (abs(TMOMZ) < 24*s.scale)
					THRUST = (abs(TMOMZ) < 10*s.scale) and $/2 or (abs(TMOMZ) < 16*s.scale) and $*2/3 or $*3/4
				end
				P_Thrust(s, GS_RAILS.ANG(mainrail), -THRUST)
			end
			
			local TOANG = R_PointToAngle2(s.x, s.y, SEG.x, SEG.y)
			local DIST = max(8*s.scale, min(24*s.scale, p.speed/3))
			s.momx,s.momy = $*3/4,$*3/4
			P_Thrust(s, TOANG, -DIST)
			S_StartSound(s, sfx_s3k8b)
			p.pflags = $|PF_JUMPDOWN & ~PF_THOKKED
			GS.cannotgrind = max($, 9)
	
			local TYPE = GS_RAILS_TYPES[RAIL.LearnRailType(mainrail or seg)] if not (TYPE) TYPE = GS_RAILS_TYPES[0] end
			if not (TYPE.landSFX==sfx_none) 
				local LANDSFX = sfx_gsral5 if (TYPE.landSFX!=nil) LANDSFX = TYPE.landSFX end
				if not (TYPE.spawnleaves) and not (TYPE.nosparks)
					S_StopSoundByID(s, sfx_gsralg)
					S_StartSoundAtVolume(s, sfx_gsralg, 130) 
				else
					S_StopSoundByID(s, LANDSFX)
					S_StartSoundAtVolume(s, LANDSFX, 104)
				end
			end
			
			AMYHEARTIMPACT(p, s, GS)
			for i = 1,5 P_SpawnSkidDust(p, 62<<16) end
			
			return true --
		end
	end
	
	--Regain ability to use hammer after flinging.
	SKIN["PostFling"] = function(p,s,GS,rail, METHOD, SLOPEFLING)
		if METHOD!="sidehop" and METHOD!="jump"
			p.pflags = $|PF_JUMPDOWN|PF_NOJUMPDAMAGE|PF_JUMPED & ~PF_THOKKED
		end
	end
	
	--Unique hangjump hop
	SKIN["PlayerThink"] = function(p, s, GS)
		if GS.hanghop and GS.railswitching
			if (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and not GS.grinding and not P_IsObjectOnGround(s)
			and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING|PF_STARTDASH)) and not p.powers[pw_carry]
				if (p.speed > 5*s.scale) and GS.railswitchANG!=nil 
					local _,P = GS_RAILS.AngleDifference(GS.railswitchANG,R_PointToAngle2(s.x,s.y, s.x+s.momx,s.y+s.momy),2,ANG60,2)
					GS.forcesprite = SPR2_SPNG
					GS.forceangle = GS.railswitchANG - (GS.railswitching*ANG1*43*P)
					
					local DIST = (48-(GS.hanghop*2))*s.scale
					local ghs = P_SpawnMobjFromMobj(s, 
					((GS.hanghop%2) and -s.momx or 0)+FixedMul(DIST, cos(GS.forceangle+ANGLE_90)), 
					((GS.hanghop%2) and -s.momy or 0)+FixedMul(DIST, sin(GS.forceangle+ANGLE_90)), (48*s.scale)-DIST, MT_LHRT)
					if ghs and ghs.valid
						ghs.target,ghs.tracer = s,s
						ghs.scale = s.scale*2/3
						ghs.destscale = 1
						ghs.fuse = 30
						ghs.renderflags = $|RF_SEMIBRIGHT
					end				
				end
			end		
		end
	end
end

if not GS_RAILS_SKINS["metalsonic"] --Already have a table.

	--Metal Sonic is difficult to handle, but can auto-accelerate gradually up to very high speeds with his thrusters!
	GS_RAILS_SKINS["metalsonic"] = {}
	local SKIN = GS_RAILS_SKINS["metalsonic"]
	
	SKIN["S_SKIN"] = {
	SIDEHOP = 38, --Superfast sidehop!
	SIDEHOPARC = 5,
	
	CROUCH = true,
	AUTOBALANCE = false,
	MUSTBALANCE = true, --Balance well or else!
	
	HEROESTWIST = 0, --Cannot Heroes Twist
	MAXSPEED = 199, --Higher maxspeed than most.
	
	SLOPEMULT = -10, --Much less affected by slopes.
	ACCELSPEED = 30, --Can self-accelerate, albeit slower than Tails.
	ACCELCAP = 79, --But to a much higher speed!
	
	NOSIDEHOPGFX = 2,
	
	SPEEDTRAIL = "none",
	}
	
	--Enter edge animation, spin horizontally and spawn purple afterimages like in the Metal Sonic fight.
	SKIN["CustomSideHop"] = function(p, s, GS, rail, SIDEJUMP)
		S_StartSoundAtVolume(s, sfx_mswarp, 69) --Play this on top of the other sound
		GS.forcesprite = SPR2_EDGE
		
		s.rollangle,s.spriteyoffset,s.spritexoffset = 0,-2<<16,0 
		s.spritexscale,s.spriteyscale = FRACUNIT+20000,FRACUNIT-5000	
		GS.stretch = {timer = 2, Xspeed = 10000, Yspeed = 2500}
		
		return "nostretch"
	end
	
	SKIN["PlayerThink"] = function(p, s, GS, rail)
		if GS.railswitching
			if (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and not GS.grinding and not P_IsObjectOnGround(s)
			and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING|PF_STARTDASH)) and not p.powers[pw_carry]
				if (GS.railswitching > 1)
					if (p.speed > 5*s.scale) and GS.railswitchANG!=nil and (RAIL) --Speen.
						local _,P = RAIL.AngleDifference(GS.railswitchANG, R_PointToAngle2(s.x,s.y, s.x+s.momx,s.y+s.momy),2,ANG60,2)
						GS.forcesprite = SPR2_EDGE
						GS.forceangle = GS.railswitchANG - (GS.railswitching*ANG1*43*P)
						
						local ghs = P_SpawnGhostMobj(s) --Metal Sonic's boss fight ghosts.
						ghs.scale = s.scale
						ghs.spritexscale,ghs.spriteyscale = s.spritexscale,s.spriteyscale
						ghs.flags = $|MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_NOCLIP|MF_NOCLIPHEIGHT
						ghs.sprite = SPR_METL
						ghs.tics,ghs.fuse = 16,16
						ghs.frame = Q|FF_FULLBRIGHT
					end
					p.pflags = $|PF_JUMPDOWN
					if (s.state!=S_PLAY_ROLL)
						if p.panim==PA_FALL or p.panim==PA_SPRING or p.panim==PA_ROLL or p.panim==PA_JUMP
							s.state = S_PLAY_ROLL
						end
					end
				elseif (p.cmd.buttons & BT_JUMP) --Auto-float afterwards if player is holding jump!
					p.pflags = $ & ~PF_JUMPDOWN
				end
			end
		end
	end
end

--Some adjustments for the fastest mod in SRB2 history!
--https://mb.srb2.org/threads/super-mystic-sonic-smsreborn-v3-2-boundless.28009/
if not GS_RAILS_SKINS["sms"]
	GS_RAILS_SKINS["sms"] = {}
	local SKIN = GS_RAILS_SKINS["sms"]
	
	SKIN["S_SKIN"] = {
	SIDEHOP = 69, --Nyoom
	SIDEHOPARC = 4,
	SIDEFLIP = SPR2_SPNG, --Ah, why not.
	SIDEHOPTIME = 8,
	
	AIRDRAG = 0,
	EMERGENCYDROP = false,
	CROUCH = true,
	CROUCHBUTTON = BT_SPIN|BT_CUSTOM1|BT_CUSTOM2,
	AUTOBALANCE = false,
	SLOPEPHYSICS = true,
	SLOPEMULT = 20,
	
	MAXSPEED = 0,
	STARTSPEEDMODIFIER = 10,
	STARTSPEEDCONDITION = 35,
	
	HEROESTWIST = 36,
	TWISTBUTTON = BT_CUSTOM1|BT_CUSTOM2,
	ACCELSPEED = 250,
	ACCELCAP = 2005,
	AUTODASHMODE = 125,
	
	NOSIDEHOPGFX = 2,
	
	SPEEDTRAIL = 110,
	SPEEDTRAILCOLOR = SKINCOLOR_SUPERGOLD2,
	}
	
	--HMS and even some SMS animations don't use SPR_PLAY, so I'm making some custom behaviour here.
	SKIN["CustomAnim"] = function(p, s, GS, rail, POSTTHINK, ANIMCALL) 
		if (POSTTHINK!=true) return false end
		if type(ANIMCALL)=="string" return end
		if p.hypermysticsonic
			s.sprite = SPR_HMSO
			if (p.cmd.buttons & BT_CUSTOM2) --Crouch, winding up for a superjump!
				s.frame = ($ & ~FF_FRAMEMASK)|F
			else
				s.frame = ($ & ~FF_FRAMEMASK)|((leveltime%2) and D or E)
			end
			GS.forcesprite = nil
		else
			if not (GS.railswitching and GS.forcesprite)
				s.sprite = SPR_SMSO --Yes, not SPR_PLAY!
				if (p.cmd.buttons & BT_CUSTOM2) --Crouch, winding up for a superjump!
					s.frame = ($ & ~FF_FRAMEMASK)|Q
					if GS.railspeed and rail --Slows you down, tho'.
						GS.railspeed = FixedDiv(FixedMul($,95),96)
					end
				elseif (p.cmd.buttons & BT_CUSTOM3) --Cool handstand trick!
					s.frame = ($ & ~FF_FRAMEMASK)|N
					if rail
						local TILT = GS.railtilt or 0
						if (TILT>=0) --What, you thought this shit was easy?!
							GS.railtilt = TILT+2
						else
							GS.railtilt = TILT-2
						end
					end
				else
					s.frame = ($ & ~FF_FRAMEMASK)|((leveltime%2) and M or L)
				end
			end
		end
		GS.animcall = "stop"
	end
	
	--First kick out legs before straightening out during a sidehop.
	SKIN["PlayerThink"] = function(p, s, GS, rail)
		if GS and ((GS.railswitching or 0) > 6) and not p.hypermysticsonic
			if (p.pflags & (PF_JUMPED|PF_NOJUMPDAMAGE)) and not GS.grinding and not P_IsObjectOnGround(s)
			and not (p.pflags & (PF_THOKKED|PF_SLIDING|PF_SPINNING|PF_STARTDASH)) and not p.powers[pw_carry]
				GS.forcesprite = SPR2_FALL
			end
		end
	end
end

--Modern Sonic stats? His boost is only included on V6's side, btw.
if not GS_RAILS_SKINS["modernsonic"] --Let Spork override this file, naturally.
	GS_RAILS_SKINS["modernsonic"] = {} 
	local SKIN = GS_RAILS_SKINS["modernsonic"]
	
	SKIN["S_SKIN"] = {
	JUMPBOOST = false, --He has a pretty low jump in Unleashed.
	SIDEHOP = 26, --Modern's sidehop is slow, but he has a big fancy jumparc!
	SIDEHOPARC = 25,
	SIDEHOPTIME = 13,
	SIDEFLIP = SPR2_SPNG, --Cool sideflip, of course!
	
	CROUCH = false,
	CROUCHBUTTON = BT_SPIN,
	AUTOBALANCE = true,
	SLOPEPHYSICS = true,
	SLOPEMULT = -45, --Lets Modern negate slope partially rather than entirely.
	
	HEROESTWIST = 1, --This thing is basically just a visual thing in Unleashed.
	TWISTBUTTON = BT_CUSTOM2,
	AUTODASHMODE = 0,
	LAUNCHPOWER = -5,
	STARTMODIFIER = -5, 
	STARTSPEEDCONDITION = 69,
	}
	
	SKIN["CustomSideHop"] = function(p, s, GS,rail, SIDEJUMP)
		if GS_RAILS.GrindState(s, "hang") return "hangflip" end
		p.finishedtrick = true
		p.jumptimer = 50
	end
end